﻿#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<list>
using namespace std;

//一、typename

//对于vector<int>我们不仅可以用提供的范围for和迭代器，我们也可以写一个print函数

void print1(const vector<int>& v)
{
	vector<int>::const_iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}
//但是如果向上面这样写就会有一个问题就是我们只能传vector<int>，如果我们想要传list或者double类型的数据，就只能重新写，这个时候我们就可以使用模板解决这个问题
template<class container>
void print2(const container& v)
{
	//编译器不知道container::const_iterator是类型还是对象
	//typename明确告诉编译器这个是类型
	typename container::const_iterator it = v.begin();//这里必须加上typename
	//或者把这个代码改成：auto it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}
//由于container没有被实例化，所以编译器不确定container::const_iterator这个是类型还是对象，加上typename就是告诉编译器这是个类型
//等传过来的实例化对象了去里面找就行

//总结：只要需要去类模板里面取内嵌的值，那么就需要加上typename


//二、非类型模板参数 

//2.1非类型模板参数

//模板参数分类类型形参与非类型形参。
//类型形参：出现在模板参数列表中，跟在class或者typename之类的参数类型名称。
//非类型形参：就是用一个常量作为类(函数)模板的一个参数，在类(函数)模板中可将该参数当成常量来使用。

//常使用在静态的栈里面，有可能我一个栈只需要10个空间，而另一个需要100个空间，所以我们需要定义一个非类型的形参
template<class T ,size_t n>
class static_stack
{
private:
	T _array[n];
	int top;
};
void test_static_stack()
{
	static_stack<int,10> st1;//10个空间的静态栈
	static_stack<int, 100> st2;//100个空间的静态栈
}
//注意：
//（1）这里面的非类型形参和普通形参不一样，这里的非类型形参是一个常量，不能进行修改
//（2）浮点数、类对象以及字符串是不允许作为非类型模板参数的，这里的非类型形参只能是整型
//（3）非类型的模板参数必须在编译期就能确认结果。

//2.2std::array

//array是一个定长的顺序容器，它有以下常用的接口
//函数声明                                  接口说明
//begin                                    正向迭代器
//end                                      正向迭代器
//rbegin                                   反向迭代器
//rend                                     反向迭代器
//size                                     array中元素的个数
//empty                                   判断是否为空
//operator[]                               []访问下标
//at（pos）                               返回pos位置的元素
//front                                   返回第一个元素
//back                                    返回最后一个元素
//date                                  返回第一个元素位置的指针

//注意：array和c语言的数组一样，唯一优点就是检查越界比c语言原生数组快。



//三、模板的特化 

//3.1 概念

//通常情况下，使用模板可以实现一些与类型无关的代码，但对于一些特殊类型的可能会得到一些错误的结
//果，需要特殊处理，比如：实现了一个专门用来进行小于比较的函数模板

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
	return left < right;
}
void test()
{
	cout << Less(1, 2) << endl; // 可以比较，结果正确

	int p1 = 1;
	int p2 = 2;
	cout << Less(&p1, &p2) << endl; // 可以比较，但结果错误
}
//可以看到，Less绝对多数情况下都可以正常比较，但是在特殊场景下就得到错误的结果。上述示例中，p1指
//向的d1显然小于p2指向的d2对象，但是Less内部并没有比较p1和p2指向的对象内容，而比较的是p1和p2
//的地址，这就无法达到预期而错误。

//此时，就需要对模板进行特化。即：在原模板类的基础上，针对特殊类型所进行特殊化的实现方式。模板特
//化中分为函数模板特化与类模板特化


//3.2 函数模板特化 

//函数模板的特化步骤：
//1. 必须要先有一个基础的函数模板
//2. 关键字template后面接一对空的尖括号 < >
//3. 函数名后跟一对尖括号，尖括号中指定需要特化的类型
//4. 函数形参表: 必须要和模板函数的基础参数类型完全相同，如果不同编译器可能会报一些奇怪的错误。

//函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
	return left < right;
}
//对less函数模板进行特化
template<>
bool Less<int*>(int* left, int* right)
{
	return *left < *right;
}
void test()
{
	cout << Less(1, 2) << endl; // 可以比较，结果正确

	int p1 = 1;
	int p2 = 2;
	cout << Less(&p1, &p2) << endl; // 调用特化之后的版本，而不走模板生成了
}
//注意：一般情况下如果函数模板遇到不能处理或者处理有误的类型，为了实现简单通常都是将该函数直接给出

bool Less1(int* left, int* right)
{
	return *left < *right;
}
//该种实现简单明了，代码的可读性高，容易书写，因为对于一些参数类型复杂的函数模板，特化时特别给
//出，因此函数模板不建议特化。


//3.3 类模板特化 

//3.3.1 全特化

//全特化即是将模板参数列表中所有的参数都确定化。

template<class T1, class T2>
class Data
{
public:
	Data() { cout << "Data<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};
//针对int、double类型的特殊处理
template<>
class Data<int, double>//全特化
{
public:
	Data() { cout << "Data<int, double>" << endl; }
private:
	int _d1;
	double _d2;
};
void TestVector()
{
	Data<int, int> d1;
	Data<int, double> d2;
}

//3.3.2 偏特化 

//偏特化：任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下模板类：
template<class T1, class T2>
class Data
{
public:
	Data() { cout << "Data<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

//偏特化有以下两种表现方式：
//（1）部分特化：将模板参数类表中的一部分参数特化。
// 将第二个参数特化为double
template <class T1>
class Data<T1, double>
{
public:
	Data() { cout << "Data<T1, double>" << endl; }
private:
	T1 _d1;
	double _d2;
};
//(2)参数更进一步的限制:偏特化并不仅仅是指特化部分参数，而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。

//两个参数偏特化为指针类型 
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
	Data() { cout << "Data<T1*, T2*>" << endl; }
private:
	T1 _d1; 
	T2 _d2;
};
//两个参数偏特化为引用类型 
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
	Data() { cout << "Data<T1&, T2&>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

//总结：
//全特化在模板的声明template处不用写模板参数，在类名的地方写上特化的类型
//偏特化要给模板参数，具体看自己的情况，并在类名的地方写参数的限定


//四、模板分离编译 

//4.1 什么是分离编译

//一个程序（项目）由若干个源文件共同实现，而每个源文件单独编译生成目标文件，最后将所有目标文件链
//接起来形成单一的可执行文件的过程称为分离编译模式。

//4.2 模板的分离编译

//假如有以下场景，模板的声明与定义分离开，在头文件中进行声明，源文件中完成定义：

//a.h中：
template<class T>
T Add(const T& left, const T& right);

//a.cpp中：
template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}

//main.cpp中：
//#include"a.h"
int main()
{
	Add(1, 2);
	Add(1.0, 2.0);
	return 0;
}
//上述将类模板进行分离编译的方式，会导致链接错误，在链接过程中，main.cpp文件中的主函数链接不上a.cpp文件中的Add函数,如下图：

//4.3解决方法 

//1. 将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的。推荐使用这种。
//2. 模板定义的位置显式实例化。这种方法不实用，不推荐使用。
//例如：
//把上述代码改成在模板定义的位置显式实例化
//a.h中：
template<class T>
T Add(const T& left, const T& right);


//a.cpp中：
template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
//显示实例化
template
T Add<int>(const T& left, const T& right);
template
T Add<double>(const T& left, const T& right);

//main.cpp中：
//#include"a.h"
int main()
{
	Add(1, 2);
	Add(1.0, 2.0);
	return 0;
}
//总结：模板声明和定义分离最好的方式是在当前的文件下进行分离

//4.4 模板总结 

//【优点】
//1. 模板复用了代码，节省资源，更快的迭代开发，C++的标准模板库(STL)因此而产生
//2. 增强了代码的灵活性

//【缺陷】
//1. 模板会导致代码膨胀问题，也会导致编译时间变长
//2. 出现模板编译错误时，错误信息非常凌乱，不易定位错误